#region Using Statements
using System;
using System.IO;
using System.Xml;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Runtime.Remoting;
using System.Reflection;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;

using SkyBurner.AI;
#endregion

namespace SkyBurner
{
    public partial class SkyBurnerWorld : BaseWorld
    {
        public enum BulletFrom
        {
            Player,
            Enemy
        }

        #region Protected attributes
        protected SkyCube m_tcSkyCube;
        protected InfiniteTerrainBase m_tcInfiniteTerrain;
        protected StarFighter m_tcStarFighter;
        protected Collection<SkyBurnerBulletBase> m_bulletList = new Collection<SkyBurnerBulletBase>();                
        protected SkyBurnerBonusManager m_bounusManager;
        protected SkyBurnerEnemyAIManager m_enemyAIManager;
        protected SkyBurnerHUD m_gameHUD;
        protected String m_exceptionMessage;
        #endregion

        #region Properties
        public SkyCube SkyCube
        {
            get { return m_tcSkyCube; }
        }

        public StarFighter Player
        {
            get { return m_tcStarFighter; }
        }
        #endregion

        public SkyBurnerWorld(BaseGame game)
            : base(game)
        {
            m_exceptionMessage = "";
            m_enemyAIManager = new SkyBurnerEnemyAIManager(m_Game, this);

            // setup camera postition...
            m_Camera.Position = new Vector3(0.0f, 1.5f, 0.0f);

            // initialize HUD
            m_gameHUD = new SkyBurnerHUD(m_Game);

            // initialize global stuff..
            SkyBurnerGlobal.Reset();
            SkyBurnerGlobal.HiScore = HiscoreTable.Instance().GetAt(0).Score;

            m_Game.RegisterObject(this);
        }

        public override void OnDeviceReset()
        {
            base.OnDeviceReset();
        }

        public void Load(String pWorld)
        {
            XmlDocument xmlWorld = new XmlDocument();

            try
            {
                String[] split;

                xmlWorld.Load(pWorld);

                XmlNode xmlRootNode = xmlWorld.DocumentElement;

                // get sunlight position
                XmlNode xmlSunLightPosition = xmlRootNode.SelectSingleNode("SunLight");                
                split = xmlSunLightPosition.SelectSingleNode("Position").InnerText.Split(' ');
                AddLight("SunLight", new BaseWorldLight(new Vector3(Convert.ToSingle(split[0]), Convert.ToSingle(split[1]), Convert.ToSingle(split[2])), Vector3.Zero));

                // setup skybox
                XmlNode xmlSkyBoxNode = xmlRootNode.SelectSingleNode("SkyBox");

                // initialize SkyCube...
                m_tcSkyCube = new SkyCube(m_Game, 
                                          xmlSkyBoxNode.SelectSingleNode("Model").InnerText, 
                                          xmlSkyBoxNode.SelectSingleNode("Texture").InnerText,
                                          xmlSkyBoxNode.SelectSingleNode("Shader").InnerText,
                                          Convert.ToSingle(xmlSkyBoxNode.SelectSingleNode("Scale").InnerText));

                // setup terrain...
                XmlNode xmlTerrainNode = xmlRootNode.SelectSingleNode("Terrain");

                // initialize terrain...                
                split = xmlTerrainNode.SelectSingleNode("Class").InnerText.Split(':');

                ObjectHandle objectHandle = Activator.CreateInstance(split[0], split[1]);
                m_tcInfiniteTerrain = (InfiniteTerrainBase)objectHandle.Unwrap();

                split = xmlTerrainNode.SelectSingleNode("Scale").InnerText.Split(' ');
                m_tcInfiniteTerrain.Initialize(m_Game,
                                               Convert.ToSingle(split[0]),
                                               Convert.ToSingle(split[1]),
                                               Convert.ToSingle(split[2]),
                                               Convert.ToInt32(xmlTerrainNode.SelectSingleNode("Size").InnerText),
                                               Convert.ToInt32(xmlTerrainNode.SelectSingleNode("PatchCount").InnerText),
                                               Convert.ToSingle(xmlTerrainNode.SelectSingleNode("Translate").InnerText));

                XmlNode xmlTerrainCustomNode = xmlTerrainNode.SelectSingleNode("Custom");
                m_tcInfiniteTerrain.Load(xmlTerrainCustomNode);

                split = xmlTerrainNode.SelectSingleNode("LightDirection").InnerText.Split(' ');
                m_tcInfiniteTerrain.LightDirection = new Vector3(Convert.ToSingle(split[0]), Convert.ToSingle(split[1]), Convert.ToSingle(split[2]));

                split = xmlTerrainNode.SelectSingleNode("LightAmbient").InnerText.Split(' ');
                m_tcInfiniteTerrain.LightAmbient = new Vector4(Convert.ToSingle(split[0]), Convert.ToSingle(split[1]), Convert.ToSingle(split[2]), Convert.ToSingle(split[3]));

                split = xmlTerrainNode.SelectSingleNode("LightDiffuse").InnerText.Split(' ');
                m_tcInfiniteTerrain.LightDiffuse = new Vector4(Convert.ToSingle(split[0]), Convert.ToSingle(split[1]), Convert.ToSingle(split[2]), Convert.ToSingle(split[3]));

                // load enemy attack waves...
                XmlNode xmlAttackWaves = xmlRootNode.SelectSingleNode("EnemyUnits");
                if (xmlAttackWaves != null)
                {
                    m_enemyAIManager.Active = (Convert.ToInt32(xmlAttackWaves.Attributes["AIActive"].Value) != 0);
                    foreach (XmlNode xmlWaveNode in xmlAttackWaves.ChildNodes)
                    {
                        String handlerClassName = xmlWaveNode.Attributes["ControllerClass"].Value;
                        String unitHandlerClassName = xmlWaveNode.Attributes["UnitControllerClass"].Value;

                        m_enemyAIManager.AddEnemyAttackWave(m_Game, handlerClassName, unitHandlerClassName, xmlWaveNode);
                    }
                }

                // load player starfighter ship...
                XmlNode xmlPlayerConfig = xmlRootNode.SelectSingleNode("Player");
                m_tcStarFighter = new StarFighter(m_Game, this, new Vector3(0.0f, 1.5f, 0.0f), xmlPlayerConfig);

                // create bonus manager...                
                m_bounusManager = new SkyBurnerBonusManager(m_Game, this);

                // check for dynamic code to execute...
                XmlNode xmlDynamicCode = xmlRootNode.SelectSingleNode("DynamicCode");
                if (xmlDynamicCode != null)
                {
                    foreach (XmlNode dcNode in xmlDynamicCode)
                    {
                        // invoke method of dynamic code...
                        String invokeMethod = dcNode.Attributes["InvokeMethod"].Value;
                        MethodInfo methodInfo =  m_dynamicCodeManager.GetType().GetMethod(invokeMethod);

                        if (methodInfo != null)
                            methodInfo.Invoke(m_dynamicCodeManager, new object[] { this, new XMLPropertyBag(dcNode) });
                        else
                            m_Game.Console.Print("SkyBurnerWorld.Load : cannot invoke dyncode method " + invokeMethod);
                    }
                }

                // ini drawable object list...
                m_drawableObjects.Add(m_tcSkyCube);
                m_drawableObjects.Add(m_tcInfiniteTerrain);                
                m_drawableObjects.Add(m_bounusManager);
                m_drawableObjects.Add(m_enemyAIManager);
                m_drawableObjects.Add(m_tcStarFighter);
            }
            catch (Exception e)
            {
                m_exceptionMessage = e.Message;
                m_Game.Console.Print("SkyBurnerWorld.Load : " + e.Message);
                m_Game.Console.Print(e.StackTrace);
            }
        }

        public void AddBullet(SkyBurnerBulletBase pBullet, BulletFrom pFrom)
        {
            m_bulletList.Add(pBullet);
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void OnUpdate(GameTime gameTime)
        {
            if (m_exceptionMessage != "")
                return;

            switch (m_tcStarFighter.Status)
            {               
                case StarFighterStatus.LevelComplete:
                case StarFighterStatus.Dead:
                    {
                        if (m_Game.UserInputState.IsUserAction(UserAction.ButtonA))
                        {
                            HiscoreTable hiscoreTable = HiscoreTable.Instance();

                            if (hiscoreTable.IsHiscore(SkyBurnerGlobal.Score))
                            {
                                SkyBurner.Screens.SkyBurnerScreenHiScores hiscoreScreen = (SkyBurner.Screens.SkyBurnerScreenHiScores)m_Game.ScreenManager.GetScreen(SkyBurner.Screens.SkyBurnerScreenID.sbs_Highscores);

                                hiscoreScreen.Mode = SkyBurner.Screens.SkyBurnerScreenHiScoresMode.Input;
                                hiscoreScreen.NewHiscore = SkyBurnerGlobal.Score;
                                m_Game.ScreenManager.ActivateScreen(SkyBurner.Screens.SkyBurnerScreenID.sbs_Highscores);                
                            }
                            else
                                m_Game.ScreenManager.ActivateScreen(SkyBurner.Screens.SkyBurnerScreenID.sbs_Main);
                        }
                        break;
                    }

                case StarFighterStatus.Escape:
                    {
                        // update graphical objects...
                        foreach (BaseDrawableObject drawableObject in m_drawableObjects)
                            drawableObject.Update(gameTime, m_Camera);

                        break;
                    }

                case StarFighterStatus.Alive:
                case StarFighterStatus.Falling:
                case StarFighterStatus.Exploding:
                    {
                        m_Game.UserInputState.Process(gameTime);

                        // update bullets and check for collisions...
                        if (m_bulletList.Count > 0)
                        {
                            int i = 0;
                            while (i < m_bulletList.Count)
                            {
                                if (m_bulletList[i].Alive)
                                {
                                    m_bulletList[i].Update(gameTime, m_Camera);

                                    bool bRemoveBullet = false;
                                    if (bRemoveBullet = m_tcStarFighter.BoundingBox.Intersect(m_bulletList[i].BoundingSphere))
                                        m_tcStarFighter.HandleCollision(m_bulletList[i]);
                                    else
                                    {
                                        // check if this collide wih some enemy...
                                        if (m_enemyAIManager.ActiveEnemyAIHandler != null)
                                        {
                                            int nEnemyUnitCount = m_enemyAIManager.ActiveEnemyAIHandler.GetEnemyUnitCount();
                                            for (int j = 0; j < nEnemyUnitCount; j++)
                                            {
                                                // check collision with player....
                                                if (m_enemyAIManager.ActiveEnemyAIHandler.GetEnemyUnitAt(j).BoundingBox.Intersect(m_tcStarFighter.BoundingBox))
                                                {
                                                    m_tcStarFighter.HandleCollisionWithEnemy(m_enemyAIManager.ActiveEnemyAIHandler.GetEnemyUnitAt(j));
                                                    m_enemyAIManager.ActiveEnemyAIHandler.EnemyUnitHandleCollisionWithPlayer(j, m_tcStarFighter);
                                                    break;
                                                }
                                                else
                                                {
                                                    // check collision with bullet...
                                                    if (bRemoveBullet = m_enemyAIManager.ActiveEnemyAIHandler.GetEnemyUnitAt(j).BoundingBox.Intersect(m_bulletList[i].BoundingSphere))
                                                    {
                                                        m_enemyAIManager.ActiveEnemyAIHandler.EnemyUnitHandleCollision(j, m_bulletList[i]);
                                                        break;
                                                    }
                                                }
                                            }
                                        }
                                    }

                                    if (bRemoveBullet)
                                    {
                                        m_bulletList[i].OnDead();
                                        m_bulletList.RemoveAt(i);
                                        continue;
                                    }
                                }
                                else
                                {
                                    m_bulletList[i].OnDead();
                                    m_bulletList.RemoveAt(i);
                                    continue;
                                }
                                i++;
                            }
                        }
                        else
                        {
                            // check collision enemy unit/player...
                            if (m_enemyAIManager.ActiveEnemyAIHandler != null)
                            {
                                int nEnemyUnitCount = m_enemyAIManager.ActiveEnemyAIHandler.GetEnemyUnitCount();
                                for (int j = 0; j < nEnemyUnitCount; j++)
                                {
                                    // check collision with player....
                                    if (m_enemyAIManager.ActiveEnemyAIHandler.GetEnemyUnitAt(j).BoundingBox.Intersect(m_tcStarFighter.BoundingBox))
                                    {
                                        m_tcStarFighter.HandleCollisionWithEnemy(m_enemyAIManager.ActiveEnemyAIHandler.GetEnemyUnitAt(j));
                                        m_enemyAIManager.ActiveEnemyAIHandler.EnemyUnitHandleCollisionWithPlayer(j, m_tcStarFighter);
                                        break;
                                    }
                                }
                            }
                        }

                        // update graphical objects...
                        foreach (BaseDrawableObject drawableObject in m_drawableObjects)
                            drawableObject.Update(gameTime, m_Camera);

                        // update AI...
                        m_enemyAIManager.Update(gameTime, m_tcStarFighter, this);
                        if (m_enemyAIManager.Active && m_enemyAIManager.NoMoreEnemy)
                            m_tcStarFighter.Status = StarFighterStatus.Escape;

                        // update camera...
                        if (m_tcStarFighter.Status != StarFighterStatus.Escape)
                            m_Camera.Set(m_tcStarFighter.Position.X, m_Camera.Position.Y, m_tcStarFighter.Position.Z - 2.0f);

                        m_Camera.Rotate(m_tcStarFighter.Roll, Vector3.UnitZ);
                        m_Camera.View = Vector3.Normalize(m_tcStarFighter.Position - m_Camera.Position);

                        break;
                    }
            }
        }

        protected void RenderSceneToTexture(GameTime gameTime, GraphicsDevice pGraphicsDevice, Camera pCamera)
        {
            foreach (BaseDrawableObject drawableObject in m_drawableObjects)
                drawableObject.Draw(gameTime, pCamera);

            foreach (SkyBurnerBulletBase bullet in m_bulletList)
                bullet.Draw(gameTime, pCamera);

            switch (m_tcStarFighter.Status)
            {
                case StarFighterStatus.Dead:
                    {
                        m_Game.BitmapFont.PrintCenterX(250, "Game Over", 48);
                        break;
                    }

                case StarFighterStatus.LevelComplete:
                    {
                        m_Game.BitmapFont.PrintCenterX(250, "Congratulations", 48);
                        m_Game.BitmapFont.PrintCenterX(310, "Level Complete", 48);
                        break;
                    }
            }
        }

        protected override void OnDraw(GameTime gameTime)
        {
            if (m_exceptionMessage != "")
            {
                m_Game.GraphicsDevice.Clear(Color.Black);
                m_Game.BitmapFont.PrintCenterX(10, "Exception caught", 10);
            }
            else
            {
                // render scene to texture...
                RenderSceneToTexture(gameTime, m_Game.GraphicsDevice, m_Camera);

                // draw HUD
                m_gameHUD.Draw(gameTime);
            }
        }
    }
}
